當專案越來越多 Components 時,為了使用 props,會將 props 層層傳入,但也許這個 Component 根本沒用到這個 props,卻為了讓下層使用,而不得不傳入,這很容易造成混淆,形成了 Props drilling
舉個例子:
App.js
import { useState } from "react";
import Header from "./Header";
import ProductList from "./ProductList";
const App = () => {
const [orders, setOrders] = useState([]);
const addOrder = (order) => {
setOrders([...orders, order]);
};
return (
<div>
<Header orders={orders} />
<ProductList addOrder={addOrder} />
</div>
);
};
export default App;
Header 組件需要傳入 orders,ProductList 組件需要傳入 addOrder,所以 state 會寫在最外層的地方,並向下傳入 props
ProductList.js
import Product from "./Product";
const ProductList = (props) => {
const { addOrder } = props;
const menu = [
{ id: 0, name: "雞肉鍋" },
{ id: 1, name: "豬肉鍋" },
{ id: 2, name: "牛肉鍋" },
{ id: 3, name: "海鮮鍋" },
{ id: 4, name: "泡菜鍋" }
];
return (
<ul>
{menu.map((item) => (
<Product key={item.id} {...item} addOrder={addOrder} />
))}
</ul>
);
};
export default ProductList;
ProductList 未使用 addOrder,但因應下層 Product 需要,再繼續往下傳遞,等於 ProductList 只是個中繼站
Product.js
const Product = (props) => {
const { addOrder, id, name } = props;
return (
<li>
<label>{name}</label>
<button onClick={() => { addOrder(id); }}>+</button>
</li>
);
};
export default Product;
這時候的 Product 才接收到從 App 到 ProductList 傳遞下來的 Props
該怎麼解決這個問題?
有的!Context API 可以解決 Props drilling
的問題!
Context API 可以「跨組件溝通傳遞資料」,讓組件可以省去組件層層傳遞的麻煩。
將 State 在最外層定義,Provider
提供 Context value 給底下的組件使用,組件完全不用傳入 Props 就可以使用到 State
useContext
是使用 Context API 的 Hook,幫助我們使用 Context API
跟著範例解說會比較清楚
現在我們有 App、Header、ProductList、Product 組件
App
載入 Header、ProductList
Header
會依據點擊次數增加數量
ProductList
渲染 Product 組件,並將菜單傳入 Product
Product
品名 + 點擊按鈕
context/index.js
import { createContext } from "react";
const context = createContext();
export const { Provider } = context;
export default context;
引入 createContext
,創建 context
,並輸出 Provider
、context
給內外層組件來使用
App.js
import { useState } from "react";
import Header from "./Header";
import ProductList from "./ProductList";
const App = () => {
const [orders, setOrders] = useState([]);
const addOrder = (order) => {
setOrders([...orders, order]);
};
return (
<div>
<Header />
<ProductList />
</div>
);
};
export default App;
載入組件,設定好要傳入的 state
import { useState } from "react";
import Header from "./Header";
import ProductList from "./ProductList";
import { Provider } from "./context";
const App = () => {
const [orders, setOrders] = useState([]);
const addOrder = (order) => {
setOrders([...orders, order]);
};
const contextValue = {
orders,
addOrder
};
return (
<div>
<Provider value={contextValue}>
<Header />
<ProductList />
</Provider>
</div>
);
};
export default App;
引入 Provider,將 state 包成 contextValue 傳入 Provider
的 value 屬性,如此一來,被 Provider 包住的組件都可以使用 contextValue 裡的 state
components/ProductList.js
import React from "react";
import Product from "./Product";
const ProductList = () => {
const menu = [
{ id: 0, name: "雞肉鍋" },
{ id: 1, name: "豬肉鍋" },
{ id: 2, name: "牛肉鍋" },
{ id: 3, name: "海鮮鍋" },
{ id: 4, name: "泡菜鍋" }
];
return (
<ul>
{menu.map((item) => (
<Product key={item.id} {...item} />
))}
</ul>
);
};
export default ProductList;
ProductList 不用傳入任何 context value
components/Product.js
import { useContext } from "react";
import context from "./context";
const Product = ({ id, name }) => {
const { addOrder } = useContext(context);
return (
<li>
<label>{name}</label>
<button onClick={() => { addOrder(id); }}>+</button>
</li>
);
};
export default Product;
Product 引入 useContext
,並將 context
傳入,這邊只需要按鈕的 function,所以只需要載入 context value 裡的 addOrder,就可以直接使用 addOrder
components/Header.js
import { useContext } from "react";
import context from "./context";
const Header = () => {
const { orders } = useContext(context);
return (
<header>
購物車 (${orders.length})
</header>
);
};
export default Header;
Header 也是同理,只需載入 context value 裡的 orders,就可以直接使用 orders
Context API 幫助我們解決跨組件溝通的問題,在後面的章節會再講到,跟 Context API 有同樣功能的 「Redux Toolkit」,不過 Redux 使用上比 Context API 複雜多了,先喘口氣留到後面再講吧
本文將同步更新至我的部落格
Lala 的前端大補帖